﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text.RegularExpressions;

namespace wxcontact
{
    public class TcpHelper
    {
        private Dictionary<Socket, ClientInfo> clientPool = new Dictionary<Socket, ClientInfo>();
        private List<SocketMessage> msgPool = new List<SocketMessage>();

        public void Run(int port)
        {
            Thread serverSocketThraed = new Thread(() =>
            {
                Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                server.Bind(new IPEndPoint(IPAddress.Any, port));
                server.Listen(10);
                server.BeginAccept(new AsyncCallback(Accept), server);
            });

            serverSocketThraed.Start();
            Console.WriteLine("Server is ready");
            Broadcast();
        }

        private void Broadcast()
        {
            Thread broadcast = new Thread(() =>
            {
                while (true)
                {
                    if (msgPool.Count > 0)
                    {
                        byte[] msg = PackageMessage(msgPool[0]);
                        foreach (KeyValuePair<Socket, ClientInfo> cs in clientPool)
                        {
                            Socket client = cs.Key;
                            if (client.Connected)
                            {
                                client.Send(msg, msg.Length, SocketFlags.None);
                            }
                        }
                        msgPool.RemoveAt(0);
                    }
                }
            });

            broadcast.Start();
        }

        private byte[] PackageMessage(SocketMessage sm)
        {
            StringBuilder packagedMsg = new StringBuilder();
            if (!sm.isLogin)
            {
                packagedMsg.AppendFormat("{0} @ {1}:\r\n    ", sm.Client.Name, sm.Time.ToShortTimeString());
                packagedMsg.Append(sm.Message);
            }
            else
            {
                packagedMsg.AppendFormat("{0} login @ {1}", sm.Client.Name, sm.Time.ToShortTimeString());
            }

            return Encoding.UTF8.GetBytes(packagedMsg.ToString());
        }

        private void Accept(IAsyncResult result)
        {
            Socket server = result.AsyncState as Socket;
            Socket client = server.EndAccept(result);
            try
            {
                server.BeginAccept(new AsyncCallback(Accept), server);
                byte[] buffer = new byte[1024];
                client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), client);
                ClientInfo info = new ClientInfo();
                info.Id = client.RemoteEndPoint;
                info.handle = client.Handle;
                info.buffer = buffer;
                this.clientPool.Add(client, info);
                Console.WriteLine(string.Format("Client {0} connected", client.RemoteEndPoint));
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error :\r\n\t" + ex.ToString());
            }
        }

        private void Recieve(IAsyncResult result)
        {
            Socket client = result.AsyncState as Socket;

            if (client == null || !clientPool.ContainsKey(client))
            {
                return;
            }

            try
            {
                int length = client.EndReceive(result);
                byte[] buffer = clientPool[client].buffer;
                client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Recieve), client);
                string msg = Encoding.UTF8.GetString(buffer, 0, length);
                SocketMessage sm = new SocketMessage();
                sm.Client = clientPool[client];
                sm.Time = DateTime.Now;

                Regex reg = new Regex(@"{<(.*?)>}");
                Match m = reg.Match(msg);
                if (m.Value != "")
                {
                    clientPool[client].NickName = Regex.Replace(m.Value, @"{<(.*?)>}", "$1");
                    sm.isLogin = true;
                    sm.Message = "login!";
                    Console.WriteLine("{0} login @ {1}", client.RemoteEndPoint, DateTime.Now);
                }
                else
                {
                    sm.isLogin = false;
                    sm.Message = msg;
                    Console.WriteLine("{0} @ {1}\r\n    {2}", client.RemoteEndPoint, DateTime.Now, msg);
                }
                msgPool.Add(sm);
            }
            catch
            {
                client.Disconnect(true);
                Console.WriteLine("Client {0} disconnet", clientPool[client].Name);
                clientPool.Remove(client);
            }
        }
    }

    public class ClientInfo
    {
        public byte[] buffer;
        public string NickName { get; set; }
        public EndPoint Id { get; set; }
        public IntPtr handle { get; set; }
        public string Name
        {
            get
            {
                if (!string.IsNullOrEmpty(NickName))
                {
                    return NickName;
                }
                else
                {
                    return string.Format("{0}#{1}", Id, handle);
                }
            }
        }
    }

    public class SocketMessage
    {
        public bool isLogin { get; set; }
        public ClientInfo Client { get; set; }
        public string Message { get; set; }
        public DateTime Time { get; set; }
    }
}